#!/usr/bin/python3.8
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 Liu Yuting All Rights Reserved #
# @Time    : 2022/10/25 15:44
# @Author  : Liu Yuting
# @Email   : 1972456540@qq.com
# @File    : Foil_Data_Process.py
# @Software: PyCharm

import hashlib
import os
import shutil

import numpy as np
from matplotlib import pyplot as plt
import re


def FoilLoader(foilpath, normalize=True):
    """ 翼型坐标数据加载

    Args:
        foilpath (str): 翼型文件(.dat)绝对路径
        normalize (bool): 标准化弦长

    Returns:
        FoilData (Dict):
            "Name" (str): 翼型名称
            "Chord" (float): 翼型弦长
            "Num" (int): 翼型坐标点个数
            "UpX" (list): 翼型上表面x坐标
            "UpY" (list): 翼型上表面Y坐标
            "LowX" (list): 翼型下表面x坐标
            "LowY" (list): 翼型下表面y坐标

    Requires:
        import re

    """

    # 读取文件
    with open(foilpath, "r") as file:
        data = file.read()
    # 将名字和数据分开
    sep = data.find("\n")
    FoilName, data = data[0:sep], data[sep + 1:]
    # 把数字之间的空格或tab全替换成tab
    c = re.compile(r"(?<=[.\d])[\s\t]+(?=[.\d-])")
    data = c.sub("\t", data)
    data = data.replace(' ', '').replace('\t', '\n').split("\n")
    data = [x for x in data if x != '']
    data = list(map(float, data))

    # 坐标点
    X, Y = data[0::2], data[1::2]
    if len(X) != len(Y):
        ex = Exception("there are different numbers of X's and Y's of foil data")
        raise ex
    else:
        Num = len(X)

    # 坐标沿x轴偏移使原点与前缘重合
    MinX = min(X)
    IndexMinX = X.index(MinX)
    X = [x - MinX for x in X]

    # 弦长标准化
    if normalize:
        ConvertRate = 1 / max(X)
        X = [x * ConvertRate for x in X]
        Y = [y * ConvertRate for y in Y]

    # 分上下表面
    if X.count(MinX) == 1:
        UpX, LowX = X[:IndexMinX + 1], X[IndexMinX:]
        UpY, LowY = Y[:IndexMinX + 1], Y[IndexMinX:]
        UpX, UpY = UpX[::-1], UpY[::-1]
    else:
        UpX, LowX = X[:IndexMinX + 1], X[IndexMinX + 1:]
        UpY, LowY = Y[:IndexMinX + 1], Y[IndexMinX + 1:]
        UpX, UpY = UpX[::-1], UpY[::-1]

    return {"Name": FoilName, "Chord": max(X)-min(X), "Num": Num,
            "UpX": UpX, "UpY": UpY, "LowX": LowX, "LowY": LowY}


def CoordinateCorrection(foilpath):
    """ 翼型坐标矫正
        对于翼型前缘不在坐标原点的情况进行旋转矫正

    Args:
        foilpath (str): 翼型文件(.dat)绝对路径

    Returns:
        FoilData (Dict):
            "Name" (str): 翼型名称
            "Chord" (float): 翼型弦长
            "Num" (int): 翼型坐标点个数
            "UpX" (list): 翼型上表面x坐标
            "UpY" (list): 翼型上表面Y坐标
            "LowX" (list): 翼型下表面x坐标
            "LowY" (list): 翼型下表面y坐标

    Requires:
        import numpy as np

    """
    foildata = FoilLoader(foilpath, normalize=False)
    UpX0 = np.array(foildata["UpX"])
    UpY0 = np.array(foildata["UpY"])
    LowX0 = np.array(foildata["LowX"])
    LowY0 = np.array(foildata["LowY"])
    Num = len(UpX0) + len(LowX0)
    trailX = (UpX0[-1] + LowX0[-1]) / 2
    trailY = 0

    # 后缘中点与(c,0)对齐
    UpY = UpY0 - (UpY0[-1] + LowY0[-1]) / 2
    LowY = LowY0 - (UpY0[-1] + LowY0[-1]) / 2

    # 计算翼型上离后缘trailX最远的点
    X = np.hstack((UpX0[::-1], LowX0[0:]))
    Y = np.hstack((UpY[::-1], LowY[0:]))
    distance2trail = ((X - trailX) ** 2 + (Y - trailY) ** 2) ** 0.5
    # distance2trail = [((X[i] - trailX) ** 2 + (Y[i] - trailY) ** 2) ** 0.5 for i in range(0, Num)]
    maxdistance2trailindex = np.where(distance2trail == np.max(distance2trail))[0][0]  # 这里返回的是元组
    maxdistance2trailX = X[maxdistance2trailindex]
    maxdistance2trailY = (Y[maxdistance2trailindex] + Y[maxdistance2trailindex + 1]) / 2

    # 计算旋转角度
    rotateangle = np.arctan(maxdistance2trailY / (trailX - maxdistance2trailX))

    # 计算旋转后坐标
    X1 = (X - trailX) * np.cos(rotateangle) - (Y - trailY) * np.sin(rotateangle) + trailX
    Y1 = (X - trailX) * np.sin(rotateangle) + (Y - trailY) * np.cos(rotateangle) + trailY

    # 前缘与原点对齐
    X1 = X1 - X1.min()

    # 坐标拆分
    UpX1, LowX1 = X1[:maxdistance2trailindex + 1], X1[maxdistance2trailindex + 1:]
    UpY1, LowY1 = Y1[:maxdistance2trailindex + 1], Y1[maxdistance2trailindex + 1:]
    UpX1, UpY1 = UpX1[::-1], UpY1[::-1]

    # 冒号之后全部删除，括号中全部删除，包括括号，不能带 \ / " ' | [ ] : , ， - . \t
    c = re.compile(r"(?<=:).*(?=.dat)")
    foilname = c.sub("", foildata["Name"])

    foilname = re.sub(r"\(.*\)", "", foilname)
    foilname = re.sub(r"\.(?!dat)", "", foilname)
    foilname = re.sub(r"[\\/\[\]\|\"\':,，\-]", "_", foilname)
    foilname = re.sub(r"[\\/\[\]\|\"\':,，\-]", "_", foilname)
    foilname = re.sub(r"[\t]", " ", foilname)

    # 去除多余的空格
    foilname = re.sub(r"\s{1,}", " ", foilname)
    foilname = re.sub(r"^\s{1,}", "", foilname)
    foilname = re.sub(r"\s{1,}(?=.dat)", "", foilname)

    #  取前20位
    foilname = foilname[0:20]
    # foilname = foildata["Name"].replace("\\", "_").replace("/", "_").replace("\"", "_").replace("\'", "_").replace("|", "_").
    newfoildata = {"Name": foilname, "Chord": X1.max()-X1.min(), "Num": len(X1),
                   "UpX": UpX1.tolist(), "UpY": UpY1.tolist(), "LowX": LowX1.tolist(), "LowY": LowY1.tolist()}

    return newfoildata


def FoilDataToDAT(foildata, targetfolderpath):
    """ 翼型数据保存为.dat文件，如果文件中存在同名文件则覆盖

    Args:
        foildata (dict): 翼型数据文件，其格式满足：
            "Name" (str): 翼型名称
            "Chord" (float): 翼型弦长
            "Num" (int): 翼型坐标点个数
            "UpX" (list): 翼型上表面x坐标
            "UpY" (list): 翼型上表面Y坐标
            "LowX" (list): 翼型下表面x坐标
            "LowY" (list): 翼型下表面y坐标
        targetfolderpath (str): 翼型保存文件夹路径

    """
    file = []
    file = file + [foildata["Name"]] + ["\n"]
    X = foildata["UpX"][::-1] + foildata["LowX"][1:]
    Y = foildata["UpY"][::-1] + foildata["LowY"][1:]
    for i in range(0, len(X)):
        file = file + ["     {:.5f}    {:.5f}\n".format(X[i], Y[i])]
    file = ''.join(file)
    with open(targetfolderpath + "//" + foildata["Name"] + ".dat", 'w') as f:
        f.write(file)


def is_number(s):
    """ 判断字符串是否为数字，其中inf，-inf不视为数字

    Args:
        s (str): 待判断的字符串

    Returns:
        True: 字符串是数字
        False: 字符串不是数字
    """

    try:
        f = float(s)
        if f != f or f == float('inf') or f == float('-inf'):
            return False
        return True
    except ValueError:
        return False


def FoilCleaning(OriFolderName, TargetFolderName, PrintInfo):
    """ 翼型dat文件数据清洗，包括剔除数据异常的翼型

    Args:
        OriFolderName (str): 原始翼型文件夹名
        TargetFolderName (str): 清洗后翼型文件夹名
        PrintInfo (bool): 是否打印问题翼型

    Returns:
        orifoilnum (int): 原始翼型文件数目
        leftfoilnum (int): 清洗后剩下的翼型文件数目

    Require:
        import shutil
        import re

    """

    foilnames = os.listdir(OriFolderName)
    orifoilnum = len(foilnames)
    leftfoilnum = 0
    for foilname in foilnames:
        # 读取文件
        foilpath = OriFolderName + "\\" + foilname
        with open(foilpath, "r") as file:
            data = file.read()
        # 将名字和数据分开
        sep = data.find("\n")
        FoilName, data = data[0:sep], data[sep + 1:]
        # 把数字之间的空格或tab全替换成tab
        c = re.compile(r"(?<=[.\d])[\s\t]+(?=[.\d-])")
        data = c.sub("\t", data)
        data = data.replace(' ', '').replace('\t', '\n').split("\n")
        data = [x for x in data if x != '']

        # 数据检查
        if [x for x in data if not is_number(x)]:  # []为空说明全为数字，否则就有非数值，不存储这个翼型
            if PrintInfo:
                print("数据错误翼型：", foilname)
            continue
        else:
            data = list(map(float, data))
            X, Y = data[0::2], data[1::2]
            if len(X) != len(Y):  # 是否X数据个数和Y数据个数相等
                if PrintInfo:
                    print("数据错误翼型", foilname)
                continue
            else:  # 复制到Cleaning_Foil文件夹
                shutil.copy(OriFolderName + "\\" + foilname, TargetFolderName + "\\" + foilname)
                leftfoilnum = leftfoilnum + 1
    return [orifoilnum, leftfoilnum]


def get_md5(s):
    """对字符串md5进行加密处理"""
    md = hashlib.md5()
    md.update(s.encode('utf-8'))
    return md.hexdigest()


def FoilRepeatCheck(FoilFolderName, PrintInfo):
    """ 翼型数据查重并删去重复

    Args:
        FoilFolderName (str): 翼型文件夹名
        PrintInfo (bool): 是否打印翼型重复信息

    """
    foilnames = os.listdir(FoilFolderName)
    orifoilnum = len(foilnames)
    orihashfoildata = {}
    for foilname in foilnames:
        # 读取文件
        foilpath = FoilFolderName + "\\" + foilname
        with open(foilpath, "r") as file:
            data = file.read()
        # 将名字和数据分开
        sep = data.find("\n")
        FoilName, data = data[0:sep], data[sep + 1:]
        # 把数字之间的空格或tab全替换成tab
        c = re.compile(r"(?<=[.\d])[\s\t]+(?=[.\d-])")
        data = c.sub("\t", data)
        data = data.replace(' ', '').replace('\t', '\n').split("\n")
        data = [x for x in data if x != '']

        # 数字统一格式
        data = ["{:.5f}".format(float(x)) for x in data]
        data = " ".join(data)
        orihashfoildata.update({foilname: get_md5(data)})

    # 去重、打印重复信息
    index = 0
    count = 0  # 重复翼型个数
    orihashfoildata = list(orihashfoildata.items())
    for i in range(0, len(orihashfoildata)):
        x = orihashfoildata[i]
        repeatfoil = {}
        for j in range(i + 1, len(orihashfoildata)):
            y = orihashfoildata[j]
            if x[0] != y[0] and x[1] == y[1]:
                repeatfoil.update(dict([y]))
                # 去重
                if os.path.exists(FoilFolderName + "\\" + y[0]):
                    count = count + 1
                    os.remove(FoilFolderName + "\\" + y[0])
        if not (repeatfoil == {}):
            index = index + 1
            if PrintInfo:
                print("{:<4d}\t原翼型：{}\n".format(index, x))
                print("\t\t重复的翼型：{}\n".format(repeatfoil))

    print("查重前共有{}个翼型\n".format(orifoilnum))
    print("去重后剩余{}个翼型\n".format(orifoilnum - count))